package com.pai4j.pgc.service.relation;

import com.pai4j.common.bean.PAIPageResponseBeanUtil;
import com.pai4j.common.constants.WebConstant;
import com.pai4j.common.enums.*;
import com.pai4j.common.enums.messagequeue.MessageQueueEnum;
import com.pai4j.common.service.messagequeue.producer.MessageQueueProducer;
import com.pai4j.common.util.DateUtils;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.common.util.ThreadPoolExecutorUtil;
import com.pai4j.domain.vo.request.mess.ChatMessageRequestVO;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.domain.vo.response.subjectaccount.SubjectBaseResponseVO;
import com.pai4j.pgc.dao.ISubjectSubcribeDAO;
import com.pai4j.pgc.entity.SubjectSubscribeEntity;
import com.pai4j.pgc.service.SubjectService;
import com.pai4j.remote.user.UserServiceClient;
import jakarta.annotation.Resource;
import jakarta.transaction.Transactional;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
 *  抽象订阅关系服务类，封装了订阅关系核心流程的公共逻辑
 *
 * @author: CYM-pai
 * @date: 2025/07/01 17:03
 **/
public abstract class AbstractSubjectRelationService implements SubjectRelationValidator, SubjectRelationWrapper {

    @Autowired
    private ISubjectSubcribeDAO subjectSubcribeDAO;
    @Autowired
    UserServiceClient userServiceClient;
    @Resource
    private SubjectService subjectService;
    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private MessageQueueProducer<ChatMessageRequestVO, Long> messageQueueProducer;

    private UserBaseResponseInfoVO sourceAccountInfo;

    private SubjectBaseResponseVO targetAccountInfo;

    protected void init(String userId ,Long subjectId) {
        this.sourceAccountInfo = userServiceClient.getUserBaseInfo(userId).getData();
        this.targetAccountInfo = subjectService.getSubjectBaseInfo(subjectId);
    }

    /**
     * 订阅关系类型，需由具体实现类返回
     *
     *
     * @return
     */
    protected abstract SubjectRelationTypeEnum getRelationType();
    
    /**
     * 是否需要异步执行后置处理流程
     *
     * @return
     */
    protected abstract boolean isAsyncDoAfterComplete();

    /**
     * 完成核心流程后的业务处理，由各实现类自己实现
     * 比如：
     * 1、基于不同关系的缓存构建、刷新
     */
    protected abstract void doAfterComplete(Long id, String userId, Long subjectId, boolean subscribe);

    /**
     * 完成核心流程后的业务处理，由各实现类自己实现
     * 比如：
     * 1、基于不同关系的缓存构建、刷新
     */
    protected void execAfterComplete(Long id, String userId, Long subjectId, boolean subscribe) {
        /**
         * 后置流程公共逻辑实现
         */
        if (!subscribe) {
            this.removeTargetAccountRelationListCache(userId, subjectId);
        }
        /**
         * 差异化逻辑各个实现类自己支持
         */
        this.doAfterComplete(id, userId, subjectId, subscribe);
    }

    /**
     * 异步执行后置业务处理流程
     *
     * @param id
     * @param userId
     * @param subjectId
     */
    protected void asyncDoAfterComplete(Long id, String userId, Long subjectId, boolean subscribe) {

        ThreadPoolExecutorUtil.execute(() -> execAfterComplete(id, userId, subjectId, subscribe));
    }

    private void doExecAfterComplete(Long id, String userId, Long subjectId, boolean subscribe) {
        if (isAsyncDoAfterComplete()) {
            this.asyncDoAfterComplete(id, userId, subjectId, subscribe);
        } else {
            this.execAfterComplete(id, userId, subjectId, subscribe);
        }
    }

    /**
     * 前置基础校验
     *
     * @param userId
     * @param subjectId
     */
    @Override
    public void validateAccountRequest(String userId, Long subjectId) {
        Assert.notNull(sourceAccountInfo, "source account is null!");
        Assert.isTrue(RoleCodeEnum.isUserRole(sourceAccountInfo.getRoleCode()), "source account role is not support!");
        Assert.notNull(targetAccountInfo, "target account is null!");
        Assert.isTrue(!RoleCodeEnum.BLACK.getCode().equals(targetAccountInfo.getRoleCode()), "target account is black list user!");
    }

    /**
     * 关系订阅接口
     *
     * @param userId
     * @param subjectId
     * @return
     */
    @Transactional
    @Override
    public Boolean subscribe(String userId, Long subjectId) {
        this.init(userId, subjectId);
        /**
         * 前置参数校验
         */
        this.validateAccountRequest(userId, subjectId);
        /**
         * 核心订阅流程处理
         */
        Long id = this.doSubscribe(userId, subjectId);
        /**
         * 执行后置流程
         */
        this.doExecAfterComplete(id, userId, subjectId, Boolean.TRUE);
        return true;
    }

    /**
     * 执行取消订阅的持久化更新逻辑
     *
     * @param userId
     * @param subjectId
     * @return
     */
    @Transactional
    @Override
    public Boolean unsubscribe(String userId,Long subjectId) {
        /**
         * 前置参数校验
         */
        this.validateAccountRequest(userId, subjectId);
        /**
         * 核心订阅流程处理
         */
        Long id = this.doUnSubscribe(userId, subjectId);
        /**
         * 执行后置流程
         */
        this.doExecAfterComplete(id, userId, subjectId, Boolean.FALSE);
        return true;
    }

    /**
     * 查询账号关系列表，走redis
     *
     * @param subjectId 用户账号
     * @param pageNo   页码
     * @param pageSize 每页大小
     * @return 分页响应对象
     */
    @Override
    public PAIPageResponseBeanUtil<UserBaseResponseInfoVO> listRelations(Long subjectId, Integer pageNo, Integer pageSize) {
        // 获取关系类型
        Integer type = this.getRelationType().getType();
        // 设置缓存键前缀
        String prefix = "SAFE";
        // 构造普通缓存键
        String cacheKey = this.getRelationListRedisKey(subjectId, type);
        // 构造安全缓存键
        String safeCacheKey = this.getRelationListRedisKey(prefix, subjectId, type);
        // 检查缓存是否存在，如果不存在则进行初始化
        if (!redisUtil.exists(cacheKey) && !redisUtil.exists(safeCacheKey)) {
            // 失效/没有好友关系, 刷新账号管理缓存列表
            this.initAccountRelationListCache(subjectId, type);
            // 设置安全缓存键过期时间为5分钟
            redisUtil.set(safeCacheKey, WebConstant.CACHE_NONE, 5 * 60);
        }
        // 获取关系总数
        Long total = redisUtil.zsize(cacheKey);
        // 初始化关系列表
        List<UserBaseResponseInfoVO> relactionAccountList = new ArrayList<>();
        // 如果总数大于0，则查询具体的关系列表
        if (total > 0) {
            // 从缓存中获取指定范围的关系列表
            Set<String> relationCaches = redisUtil.zreverseRange(cacheKey, (pageNo - 1) * pageSize, pageNo * pageSize);
            // 批量获取用户信息
            relactionAccountList = userServiceClient.batchGetList((List<String>) relationCaches).getData();
        }
        // 返回分页响应对象
        return PAIPageResponseBeanUtil.success(pageNo, pageSize, total, relactionAccountList);
    }
    
    /**
     * 查询专题下的所有订阅用户列表
     *
     * @param subjectId 用户账号
     * @return 订阅用户列表
     */
    @Override
    public Set<String> getAllSubscriber(Long subjectId) {
        // 获取关系类型
        Integer type = this.getRelationType().getType();
        // 设置缓存键前缀
        String prefix = "SAFE";
        // 构造普通缓存键
        String cacheKey = this.getRelationListRedisKey(subjectId, type);
        // 构造安全缓存键
        String safeCacheKey = this.getRelationListRedisKey(prefix, subjectId, type);
        // 检查缓存是否存在，如果不存在则进行初始化
        if (!redisUtil.exists(cacheKey) && !redisUtil.exists(safeCacheKey)) {
            // 失效/没有订阅关系, 刷新账号管理缓存列表
            this.initAccountRelationListCache(subjectId, type);
            // 设置安全缓存键过期时间为5分钟
            redisUtil.set(safeCacheKey, WebConstant.CACHE_NONE, 5 * 60);
        }
        // 返回所有订阅用户列表
        return redisUtil.zreverseRange(cacheKey, 0, Integer.MAX_VALUE);
    }
    
    /**
     * 检查用户是否订阅了指定专题
     *
     * @param userId 用户账号
     * @param subjectId     指定专题账号
     * @return 订阅关系存在性
     */
    @Override
    public boolean isSubscribe(String userId, Long subjectId) {
        // 获取关系类型
        Integer type = this.getRelationType().getType();
        // 设置缓存键前缀
        String prefix = "SAFE";
        // 构造普通缓存键
        String cacheKey = this.getRelationListRedisKey(subjectId, type);
        // 构造安全缓存键
        String safeCacheKey = this.getRelationListRedisKey(prefix, subjectId, type);
        // 检查缓存是否存在，如果不存在则进行初始化
        if (!redisUtil.exists(cacheKey) && !redisUtil.exists(safeCacheKey)) {
            // 失效/没有好友关系, 刷新账号管理缓存列表
            this.initAccountRelationListCache(subjectId, type);
            // 设置安全缓存键过期时间为5分钟
            redisUtil.set(safeCacheKey, WebConstant.CACHE_NONE, 5 * 60);
        }
        // 检查用户是否存在于订阅列表中
        return redisUtil.zIsExist(cacheKey, userId);
    }

    /**
     * 添加usrId到subjectId的专题列表缓存
     *
     * @param userId
     * @param subjectId
     */
    protected void addTargetAccountRelationListCache(String userId, Long subjectId) {
        // 获取好友列表redis key
        SubjectRelationTypeEnum relationType = getRelationType();
        String cacheKey = this.getRelationListRedisKey(subjectId, relationType.getType());
        if (!redisUtil.exists(cacheKey)) {
            // 第一次订阅或者缓存失效
            // 查询数据库，主动刷新redis
            this.initAccountRelationListCache(subjectId, relationType.getType());
            redisUtil.zadd(cacheKey, userId, DateUtils.getCurrentTimeMillis(),
                    RedisKeyEnum.ACCOUNT_RELATION_LIST_CACHE.getExpireTime());
        }
        /**
         * 添加targetAccount到sourceAccount的好友列表缓存，当前时间作为sorted set 的score，后续可用于倒序查询
         */
        redisUtil.zadd(cacheKey, userId, DateUtils.getCurrentTimeMillis(),
                RedisKeyEnum.ACCOUNT_RELATION_LIST_CACHE.getExpireTime());
    }

    /**
     * 删除用户关系账号
     *
     * @param userId
     * @param subjectId
     */
    protected void removeTargetAccountRelationListCache(String userId, Long subjectId) {
        // 获取好友列表redis key
        SubjectRelationTypeEnum relationType = SubjectRelationTypeEnum.getByTargetAccountRoleCode(
                targetAccountInfo.getRoleCode());
        String cacheKey = this.getRelationListRedisKey(subjectId, relationType.getType());
        redisUtil.zremove(cacheKey, String.valueOf(subjectId));
    }
    
    
    /**
     * 初始化主动刷新专题订阅列表缓存
     *
     * @param subjectId 账号标识，用于查询专题订阅列表
     * @param type    账号类型，用于区分不同的账号关系列表
     */
    public void initAccountRelationListCache(Long subjectId, Integer type) {
        // 根据账号和类型获取账号关系列表
        List<SubjectSubscribeEntity> accountRelationEntities =
                this.getAccountRelationList(subjectId, type);
        // 如果账号关系列表为空，则直接返回，无需进一步处理
        if (CollectionUtils.isEmpty(accountRelationEntities)) {
            return;
        }
        // 生成缓存键
        String cacheKey = this.getRelationListRedisKey(subjectId, type);
        // 将账号关系列表转换为Redis ZSet数据结构所需的格式
        Set<ZSetOperations.TypedTuple<String>> tuples = accountRelationEntities.stream()
                .map(ar -> {
                    // 根据账号关系实体生成ZSet成员和分数
                    String value = ar.getUserId();
                    // 根据专题订阅时间作为分数
                    Double score = Double.valueOf(ar.getApplyDate().getTime());
                    return new DefaultTypedTuple<>(value, score);
                }).collect(Collectors.toSet());
        // 使用Redis工具类将数据添加到ZSet中，并设置缓存过期时间
        redisUtil.zadd(cacheKey, tuples, RedisKeyEnum.ACCOUNT_RELATION_LIST_CACHE.getExpireTime());
    }
    /**
     * 生成关系列表的Redis键
     * 此方法用于生成固定格式的Redis键，以便在Redis中存储或获取关系列表数据
     * 它基于源账户和关系类型，结合固定的前缀，创建一个唯一的键
     *
     * @param subjectId 源账户标识，用于区分不同的账户
     * @param relationType 关系类型，表示账户之间的不同关系，如朋友、关注等
     * @return 返回生成的Redis键
     */
    private String getRelationListRedisKey(Long subjectId, Integer relationType) {
        // 定义数据前缀，用于标识数据类型
        String prefix = "DATA";
        // 调用重载方法，传入数据前缀、源账户和关系类型，生成并返回Redis键
        return this.getRelationListRedisKey(prefix, subjectId, relationType);
    }
    
    /**
     * 生成关系列表的Redis键
     * 此方法是重载方法，允许传入自定义的前缀参数，以支持更灵活的键名生成
     * 它结合前缀、类型前缀、关系类型、账户前缀和源账户，生成一个唯一的Redis键
     *
     * @param prefix 自定义前缀，用于支持更灵活的键名格式
     * @param subjectId 源账户标识，用于区分不同的账户
     * @param relationType 关系类型，表示账户之间的不同关系，如朋友、关注等
     * @return 返回生成的Redis键
     */
    private String getRelationListRedisKey(String prefix, Long subjectId, Integer relationType) {
        // 定义类型前缀，用于标识关系类型
        String typePrefix = "T";
        // 定义账户前缀，用于标识账户部分的开始
        String accountPrefix = "AC";
        // 使用枚举类RedisKeyEnum中的方法生成最终的Redis键
        // 这里使用了枚举类来管理键名格式，以便在代码的其他部分中保持一致性和减少硬编码
        return RedisKeyEnum.ACCOUNT_RELATION_LIST_CACHE.getKey(
                prefix,
                typePrefix,
                String.valueOf(relationType),
                accountPrefix,
                String.valueOf(subjectId));
    }


    /**
     * 根据关系ID获取实体信息
     *
     * @param relationId
     * @return
     */
    public SubjectSubscribeEntity getRelationEntityById(Long relationId) {

        return subjectSubcribeDAO.findById(relationId).orElse(null);
    }

    /**
     * 更新关系实体信息
     *
     * @param updateSubjectRelationEntity
     * @return
     */
    public SubjectSubscribeEntity updateSubjectRelationEntity(SubjectSubscribeEntity updateSubjectRelationEntity) {

        Assert.notNull(updateSubjectRelationEntity, "entity is null!");
        return subjectSubcribeDAO.save(updateSubjectRelationEntity);
    }

    /**
     * 查询数据库，获取当前专题的用户订阅列表
     *
     * @param subjectId 专题账号，用于查询订阅信息
     * @param type    订阅类型，用于过滤订阅列表
     * @return 返回专题订阅的用户列表，如果列表为空，则返回空列表
     */
    public List<SubjectSubscribeEntity> getAccountRelationList(Long subjectId, Integer type) {
    
        return subjectSubcribeDAO.findAllBySubjectIdAndType(subjectId, type);
    }
    
    /**
     * 执行订阅的持久化更新逻辑
     *
     * @param userId 发起订阅的账号
     * @param subjectId 被订阅的账号
     * @return 返回订阅关系的ID，表示订阅关系在数据库中的唯一标识
     */
    private Long doSubscribe(String userId, Long subjectId) {
        // 获取订阅关系实体，用于后续的订阅关系处理
        SubjectSubscribeEntity entity = this.getSubjectRelationEntity(userId, subjectId);
        // 确保订阅关系不存在或不是已确认状态，防止重复订阅
        Assert.isTrue(entity == null || !SubjectRelationStatusEnum.isConfirmed(entity.getStatus()), "重复订阅！");
        if (entity != null) {
            // 如果订阅关系已存在但未确认，此处可添加更新逻辑（例如更新订阅状态），省略以简化代码
            entity.setType(SubjectRelationTypeEnum.USER_SUBSCRIBE_SUBJECT.getType());
            entity.setStatus(SubjectRelationStatusEnum.CONFIRMED.getStatus());
            entity.setApplyDate(new Date());
            return entity.getId();
        } else {
            // 如果订阅关系不存在，创建新的订阅关系实体
            entity = buildSubjectRelationEntity(userId, subjectId);
        }
        // 将订阅关系实体保存到数据库，并返回订阅关系的ID
        return this.saveSubjectRelation2DB(entity);
    }


    /**
     * 执行取消订阅的持久化更新逻辑
     * @param userId
     * @param subjectId
     * @return
     */
    private Long doUnSubscribe(String userId, Long subjectId) {
        SubjectSubscribeEntity entity = this.getSubjectRelationEntity(userId, subjectId);
        Assert.isTrue(entity != null && SubjectRelationStatusEnum.isConfirmed(entity.getStatus()), "未订阅，取消操作失败！");
        entity.setStatus(SubjectRelationStatusEnum.REJECTED.getStatus());
        entity.setCancleDate(new Date());
        return this.saveSubjectRelation2DB(entity);
    }
    
    /**
     * 获取专题订阅关系实体
     * 此方法用于根据源账号（sourceAccount）和目标账号（targetAccount）获取它们之间的订阅关系实体
     * 它主要用于处理和查询用户对特定专题之间的订阅关系
     *
     * @param userId 源账号标识，通常是一个字符串形式的用户标识
     * @param subjectId 目标账号标识，通常是一个长整型的用户标识
     * @return 返回一个SubjectSubscribeEntity对象，表示源账号和目标账号之间的订阅关系如果不存在这样的关系，则返回null
     */
    private SubjectSubscribeEntity getSubjectRelationEntity(String userId, Long subjectId) {
        return subjectSubcribeDAO.findAllByUserIdAndSubjectId(userId, subjectId);
    }

   /**
    *  将专题订阅关系实体保存到数据库
    *
    * @author: CYM-pai
    * @date: 2025/05/17 14:00
    **/
    private Long saveSubjectRelation2DB(SubjectSubscribeEntity entity) {

        return subjectSubcribeDAO.save(entity).getId();
    }

    
    /**
     *  构建专题订阅关系实体
     *
     * @author: CYM-pai
     * @date: 2025/05/17 13:56
     **/
    private SubjectSubscribeEntity buildSubjectRelationEntity(String userId, Long subjectId) {
        SubjectSubscribeEntity entity = new SubjectSubscribeEntity();
        Date now = new Date();
        entity.setUserId(String.valueOf(userId));
        entity.setSubjectId(subjectId);
        entity.setType(SubjectRelationTypeEnum.USER_SUBSCRIBE_SUBJECT.getType());
        entity.setStatus(SubjectRelationStatusEnum.CONFIRMED.getStatus());
        entity.setApplyDate(now);
        return entity;
    }

    /**
     * 专题订阅成功等将 userId添加到subjectId最新的订阅列表缓存
     *
     * @param userId
     * @param subjectId
     */
    protected void addTargetAccount2SourceLastSubscribeList(String userId, Long subjectId) {

        this.addTargetAccount2SourceLastSubscribeList(userId, subjectId, System.currentTimeMillis());
    }

    protected void addTargetAccount2SourceLastSubscribeList(String userId, Long subjectId, Long lastTime) {
        /**
         * 专题订阅成功，走MQ通知UGC服务，需要刷新订阅列表缓存
         */
        ChatMessageRequestVO message = new ChatMessageRequestVO();
        message.setSenderId(userId);
        message.setReceiverId(String.valueOf(subjectId));
        message.setTime(lastTime);
        messageQueueProducer.send(MessageQueueEnum.QUEUE_SUBSCRIBE_LIST_REFRESH, message);
    }
}
